/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.connect5.intervals;

import java.util.ArrayList;
import java.util.HashMap;
import org.jfonia.connect5.basics.AbstractValueNode;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.BasicNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.ValueNode;
import org.jfonia.connect5.relations.Equal;

/**
 *
 * @author installer
 */
public class LinkedLongIntervalList implements Interval{
    MutableValueNode<Long> begin = new BasicValueNode<Long>(0l);
    MutableValueNode<Long> end   = new BasicValueNode<Long>(0l);
    ValueNode<Long> difference   = new AbstractValueNode<Long>()
    {
            public Long getValue()
            {
                    return end.getValue() - begin.getValue();
            }

            public Class<?> getType()
            {
                    return Long.class;
            }
    };

    ArrayList<Interval<Long>> elements = new ArrayList<Interval<Long>>(); // n
    HashMap<MutableValueNode<Long>, Equal<Long>> relations = new HashMap<MutableValueNode<Long>, Equal<Long>>(); // relation[i] is equality i-1,i

    public LinkedLongIntervalList()
    {
            makeLink(0);
    }


    public MutableValueNode<Long> getBeginNode()
    {
            return begin;
    }

    public MutableValueNode<Long> getEndNode()
    {
            return end;
    }

    public ValueNode<Long> getDifferenceNode()
    {
            return difference;
    }

    public Long getBegin()
    {
            return begin.getValue();
    }

    public Long getEnd()
    {
            return end.getValue();
    }

    public Long getDifference()
    {
            return difference.getValue();
    }

    public int size()
    {
            return elements.size();
    }

    public Interval<Long> add(Interval<Long> ts)
    {
            return insert(size(), ts);
    }

    public Interval<Long> insert(int pos, Interval<Long> val)
    {
            if (pos < 0 || pos > elements.size())
                    throw new IndexOutOfBoundsException();
            removeLink(pos);
            elements.add(pos, val);
            makeLink(pos);
            makeLink(pos + 1);            
            return val;
    }

    public Interval<Long> remove(int i)
    {
            assert (i >= 0 && i < elements.size());

            removeLink(i);
            removeLink(i + 1);
            Interval<Long> val = elements.remove(i);
            makeLink(i);
            //assert relations.size() == elements.size() + 1;
            return val;
    }


    protected MutableValueNode<Long> getRight(int pos)
    {
            return pos < this.size() ? elements.get(pos).getBeginNode() : this.end;
    }

    protected MutableValueNode<Long> getLeft(int pos)
    {
            return pos > 0 ? elements.get(pos - 1).getEndNode() : this.begin;
    }

    protected void makeLink(int pos)
    {
            assert(pos >= 0 && pos <= size());
            MutableValueNode<Long> left = getLeft(pos);
            MutableValueNode<Long> right = getRight(pos);
            relations.put(left, new Equal<Long>(left, right)); // right is set equal to left
    }

    protected void removeLink(int pos)
    {
            assert(pos >= 0 && pos <= size());
            MutableValueNode<Long> left = getLeft(pos);
            MutableValueNode<Long> right = getRight(pos);
            Equal eq = relations.remove(left);
            if(eq != null){
                eq.switchOff();
            }
            
    }

    public Interval<Long> getIntervalFromIndex(int index){
        return elements.get(index);
    }

    public Interval<Long> getIntervalFromValue(long value){
        if(value >= begin.getValue() && value <= end.getValue()){
            int pos = 0;
            while(pos < elements.size() && elements.get(pos).getBegin() <= value){
                pos++;
            }
            pos--;
            return elements.get(pos);
        }else{
            throw new IndexOutOfBoundsException("index "+value+", not in ["+begin.getValue()+","+end.getValue()+"]");
        }
    }

    public int indexOf(Interval<Long> interval){
        return elements.indexOf(interval);
    }


    public void stretch(long newDifference){
        long currentLength = difference.getValue();
        double factor = (double)newDifference/currentLength;
        for(int i = 0; i < elements.size(); i++){
            Interval<Long> val = elements.get(i);
            long diff = (long)(factor * val.getDifference());
            val.getEndNode().setValue(val.getBegin()+diff);
        }
        getEndNode().setValue(newDifference);
    }
    
}
